/*********************************************
 * vim:sw=8:ts=8:si:et
 * To use the above modeline in vim you must have "set modeline" in your .vimrc
 * Author: Guido Socher 
 * Modified by Guenther Hoelzl
 * Ported to C# and modified for NETMF by hanzibal in 2011 with permission from
 * Guenther Hoelzl
 * Copyright: GPL V2
 * http://www.gnu.org/licenses/gpl.html
 *
 * Based on the enc28j60.c file from the AVRlib library by Pascal Stang.
 * For AVRlib See http://www.procyonengineering.com/
 * Used with explicit permission of Pascal Stang.
 *
 * See the Microchip PDF for specs on the ENC28J60 here:
 * http://ww1.microchip.com/downloads/en/devicedoc/39662a.pdf
 * 
 * Title: Microchip ENC28J60 Ethernet Interface Driver
 * Chip type           : ATMEGA88 with ENC28J60
 *********************************************/

using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;


internal class ENC28J60Driver
{
    byte Enc28j60Bank;
    ushort NextframePtr;

    SPI spi;
    OutputPort reset;
    InterruptPort irq;

    byte[] spibuf = new byte[3];

    /// <summary>
    /// Event handler for link status changes
    /// </summary>
    /// <param name="sender">The ENC28J60 chip instance from which the event originates</param>
    /// <param name="time">Time when the event occurred</param>
    /// <param name="isUp">Indicates link status</param>
    public delegate void LinkChangedEventHandler(ENC28J60Driver sender, DateTime time, bool isUp);

    /// <summary>
    /// Fires when the phy goes up or down
    /// </summary>
    public event LinkChangedEventHandler OnLinkChangedEvent;

    /// <summary>
    /// Event handler for incominig ethernet frames
    /// </summary>
    /// <param name="sender">The ENC28J60 chip instance from which the event originates</param>
    /// <param name="time">Time when the event occurred</param>
    public delegate void FrameArrivedEventHandler(ENC28J60Driver sender, byte[] frame, DateTime timeReceived);

    /// <summary>
    /// Fires when one or more incoming frames have arrived from network
    /// </summary>
    public event FrameArrivedEventHandler OnFrameArrived;

    /// <summary>
    /// Creates a new ENC28J60 driver object instance, the chip will be held
    /// in reset until the Start method is called
    /// </summary>
    /// <param name="resetPin">Netduino pin to use for chip reset</param>
    /// <param name="irqPin">Netduino pin to use for interrupt request</param>
    /// <param name="csPin">Netduino pin to use for SPI chip select</param>
    /// <param name="spiModule">Netduino SPI module to use</param>
    public ENC28J60Driver(Cpu.Pin irqPin, Cpu.Pin csPin, SPI.SPI_module spiModule)
    {

        irq = new InterruptPort(irqPin, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow);
        irq.OnInterrupt += new NativeEventHandler(irq_OnInterrupt);

        var cfg = new SPI.Configuration(csPin, false, 0, 0, false, true, 12000, spiModule);
        spi = new SPI(cfg);
    }

    /// <summary>
    /// Releases the chip from reset and starts up the driver, after calling
    /// this method, it normally takes a few seconds before link is actually up
    /// </summary>
    /// <param name="mac">Six byte array containing MAC address</param>
    public void Start(byte[] mac)
    {
        //reset.Write(true);
        Thread.Sleep(10);
        Init(mac);
    }

    /// <summary>
    /// Get the revision id of the chip, see current silicon errata @ Microchip
    /// </summary>
    public byte RevId
    {
        get
        {
            return ReadReg(EREVID);
        }
    }

    /// <summary>
    /// Gets a boolean indicating if link is currently up
    /// </summary>
    public bool IsLinkUp
    {
        get
        {
            return (ReadPhyReg(PHSTAT2) & PHSTAT2_LSTAT) != 0;
        }
    }

    // hanzibal: added interrupts and event routing
    // TODO: Here's a potential bug. Since async events does not seem
    // to be supported by framwork, we should fifo them to be called
    // from outside this ISR - this in order not to mess up the register bank
    // of the chip
    void irq_OnInterrupt(uint data1, uint data2, System.DateTime time)
    {
        //DBG("IRQ occurred");

        byte flags;
        bool evtlink = false;

        // this lock avoids interleaved register bank switching
        lock (irq)
        {
            // save current bank
            var pushb = CurrentBank;

            if (((flags = ReadReg(EIR)) & EIR_LINKIF) != 0)
            {
                //DBG("IRQ_LINK: " + (IsLinkUp ? "up" : "down"));
                ReadPhyReg(PHIR);
                // it seems async events are not supported and we
                // can't fire syncronous events while holding bank
                evtlink = true;
            }

            // done using register bank, so restore it
            CurrentBank = pushb;
        }

        // now we can fire events
        if (evtlink && OnLinkChangedEvent != null) OnLinkChangedEvent.Invoke(this, time, IsLinkUp);

        var pc = ReadReg(EPKTCNT);

        if ((flags & EIR_PKTIF) != 0)
        {
            // We have 1 or more frames in the buffer.  
            //DBG("IRQ_PKT: frame(s) arrived");
            if (OnFrameArrived != null)
            {
                byte[] frame = ReceiveFrame();

                while (frame != null && frame.Length > 0)
                {
                    OnFrameArrived.Invoke(this, frame, time);
                    frame = ReceiveFrame();
                }
            }
        }

        // reset NETMF irq
        irq.ClearInterrupt();
    }

    byte ReadOp(byte op, byte address)
    {
        // mac, phy and mii registers returns a dummy byte
        // before acutal data byte
        int start_rd = (address & 0x80) == 0 ? 0 : 1;

        // mask out register and add opcode
        spibuf[0] = (byte)(op | (address & 0x1f));
        spi.WriteRead(spibuf, 0, 2, spibuf, 0, 2, start_rd);
        return spibuf[1];
    }

    void WriteOp(byte op, byte address, byte data)
    {
        spibuf[0] = (byte)(op | (address & 0x1f));
        spibuf[1] = data;
        spi.WriteRead(spibuf, 0, 2, null, 0, 0, 2);
    }

    void ReadBuffer(ushort len, byte[] data)
    {
        spibuf[0] = ENC28J60_READ_BUF_MEM;
        spi.WriteRead(spibuf, 0, 1, data, 0, len, 1);
        //data[len] = 0;
    }

    void WriteBuffer(byte[] data)
    {
        byte[] op = { ENC28J60_WRITE_BUF_MEM };
        //op.CopyTo(data, 0);
        //var result = Combine(op, data);

        //byte[] op = { 0x00, 0x00, 0x01, 0x02, 0x03 };
        //spi.Write(op);
        //spi.Write(data);
        byte[] status = new byte[0];
        spi.WriteRead(Utility.CombineArrays(op, data), status);
        //spi.WriteRead(data, status);

    }

    byte CurrentBank
    {
        get
        {
            return (byte)((ReadOp(0, ECON1) & 0x03) << 5);
        }
        set
        {
            value &= BANK_MASK;
            // this lock serves to avouid interleaved bank swithing on the chip
            // should an interrupt occur
            lock (irq)
            {
                // only set the bank when needed
                if (value == Enc28j60Bank) return;
                // set the bank
                WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1 | ECON1_BSEL0));
                WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, (byte)(value >> 5));
                Enc28j60Bank = value;
                Debug.Assert(value == CurrentBank, "Verify that you have selected the correct InterfaceProfile in the Networking.Adapter.Start method");
            }
        }
    }

    byte ReadReg(byte address)
    {
        // set the bank
        CurrentBank = address;
        // do the read
        return ReadOp(ENC28J60_READ_CTRL_REG, address);
    }

    void WriteReg(byte address, byte data)
    {
        // set the bank
        CurrentBank = address;
        // do the write
        WriteOp(ENC28J60_WRITE_CTRL_REG, address, data);
    }

    void WritePhyReg(byte address, ushort data)
    {
        // set the PHY register address
        WriteReg(MIREGADR, address);
        // set the write command
        WriteReg(MICMD, 0);
        // write the PHY data
        WriteReg(MIWRL, (byte)data);
        WriteReg(MIWRH, (byte)(data >> 8));
        // wait until the PHY write completes
        do
        {
            Thread.Sleep(1); //delayMicroseconds(15);
        }
        while ((ReadReg(MISTAT) & MISTAT_BUSY) != 0);
    }

    // added reading of phy registers /hanzibal
    private ushort ReadPhyReg(byte address)
    {
        // set the PHY register address
        WriteReg(MIREGADR, address);
        // set the read command
        WriteReg(MICMD, MICMD_MIIRD);
        // wait until the PHY write completes
        do
        {
            Thread.Sleep(1); //delayMicroseconds(15);
        }
        while ((ReadReg(MISTAT) & MISTAT_BUSY) != 0);

        // clear the read command
        WriteReg(MICMD, 0);

        // read the PHY data
        return (ushort)(ReadReg(MIRDL) | (ReadReg(MIRDH) << 8));
    }

    /// <summary>
    /// Gets or sets the PLL clock multiplier for external clock
    /// </summary>
    public byte ClockOut
    {
        get
        {
            return ReadReg(ECOCON);
        }
        set
        {
            //setup clkout: 2 is 12.5MHz:
            WriteReg(ECOCON, (byte)(value & 0x7));
        }
    }

    void Init(byte[] macaddr)
    {
        var irqLink = ReadPhyReg(PHIE);

        var val = (ushort)(irqLink | 18);

        WritePhyReg(PHIE, val);


        // perform system reset
        WriteOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
        Thread.Sleep(1); // TOOD: delay(50);

        // check CLKRDY bit to see if reset is complete
        // The CLKRDY does not work. See Rev. B4 Silicon Errata point. Just wait.
        //while(!(enc28j60Read(ESTAT) & ESTAT_CLKRDY));
        // do bank 0 stuff
        // initialize receive buffer
        // 16-bit transfers, must write low byte first
        // set receive buffer start address
        NextframePtr = RXSTART_INIT;
        // Rx start
        WriteReg(ERXSTL, RXSTART_INIT & 0xFF);
        WriteReg(ERXSTH, RXSTART_INIT >> 8);
        // set receive pointer address
        WriteReg(ERXRDPTL, RXSTART_INIT & 0xFF);
        WriteReg(ERXRDPTH, RXSTART_INIT >> 8);
        // RX end
        WriteReg(ERXNDL, RXSTOP_INIT & 0xFF);
        WriteReg(ERXNDH, RXSTOP_INIT >> 8);
        // TX start
        WriteReg(ETXSTL, TXSTART_INIT & 0xFF);
        WriteReg(ETXSTH, TXSTART_INIT >> 8);
        // TX end
        WriteReg(ETXNDL, TXSTOP_INIT & 0xFF);
        WriteReg(ETXNDH, TXSTOP_INIT >> 8);


        // do bank 1 stuff, frame filter:
        // For broadcast frames we allow only ARP packtets
        // All other frames should be unicast only for our mac (MAADR)
        //
        // The pattern to match on is therefore
        // Type     ETH.DST
        // ARP      BROADCAST
        // 06 08 -- ff ff ff ff ff ff -> ip checksum for theses bytes=f7f9
        // in binary these poitions are:11 0000 0011 1111
        // This is hex 303F->EPMM0=0x3f,EPMM1=0x30
        // WriteReg(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN);
        // WriteReg(EPMM0, 0x3f);
        // WriteReg(EPMM1, 0x30);
        // WriteReg(EPMCSL, 0xf9);
        // WriteReg(EPMCSH, 0xf7);

        // Modification by Guenther Hoelzl
        // No special frame filter for ipv6EtherShield
        // hanzibal: Well, Guenther this is not just ipv6EtherShield anymore 
        // and so the filter shoul be user configurable in future.
        // UCEN = Unicast MAC Addresses are enabled
        // BCEN = Broadcast MAC Address is enabled
        // MCEN = Multicast MAC Addresses are enabled
        //WriteReg(ERXFCON, ERXFCON_UCEN | ERXFCON_BCEN | ERXFCON_MCEN);

        // Valkyrie-MT: Based on empirical evidence, I believe that the MCEN flag uses the Least significant byte instead of the Most significant byte
        // Making it very much broken.  So, I am using the pattern match instead...

        // Valkyrie-MT: This allows for MDNS and LLMNR IPv4 multicast through pattern match
        WriteReg(ERXFCON, ERXFCON_UCEN | ERXFCON_BCEN | ERXFCON_PMEN | ERXFCON_CRCEN); // unicast, broadcast and pattern match (for multicast), and CRC checking
        WriteReg(EPMOH, 0x00);  // Offset
        WriteReg(EPMOL, 0x00);
        
        WriteReg(EPMM0, 0x07);  // Bitmask for which bytes are used in the Pattern Match
        WriteReg(EPMM1, 0x00);
        WriteReg(EPMM2, 0x00);
        WriteReg(EPMM3, 0xc0);
        WriteReg(EPMM4, 0x01);
        WriteReg(EPMM5, 0x00);
        WriteReg(EPMM6, 0x00);
        WriteReg(EPMM7, 0x00);

        WriteReg(EPMCSH, 0xa0);  // Checksum for the pattern match
        WriteReg(EPMCSL, 0x1f);  // I used Windows Calc to sum 4 byte groups in hex, then used the "Not" function for 1's Complement


        // Valkyrie-MT: This allows for All IPv4 multicast through pattern match
        //WriteReg(ERXFCON, ERXFCON_UCEN | ERXFCON_BCEN | ERXFCON_PMEN); // unicast, broadcast and pattern match (for multicast)
        //WriteReg(EPMOH, 0x00);  // Offset
        //WriteReg(EPMOL, 0x00);

        //WriteReg(EPMM0, 0x07);
        //WriteReg(EPMM1, 0x00);
        //WriteReg(EPMM2, 0x00);
        //WriteReg(EPMM3, 0x00);
        //WriteReg(EPMM4, 0x00);
        //WriteReg(EPMM5, 0x00);
        //WriteReg(EPMM6, 0x00);
        //WriteReg(EPMM7, 0x00);

        //WriteReg(EPMCSH, 0xa0);
        //WriteReg(EPMCSL, 0xff);


        //
        //
        // do bank 2 stuff
        // enable MAC receive
        WriteReg(MACON1, MACON1_MARXEN | MACON1_TXPAUS | MACON1_RXPAUS);

        // bring MAC out of reset
        WriteReg(MACON2, 0x00);
        
        // enable automatic padding to 60bytes and CRC operations
        WriteOp(ENC28J60_BIT_FIELD_SET, MACON3, MACON3_PADCFG0 | MACON3_TXCRCEN | MACON3_FRMLNEN);
        
        // set inter-frame gap (non-back-to-back)
        WriteReg(MAIPGL, 0x12);
        WriteReg(MAIPGH, 0x0C);
        // set inter-frame gap (back-to-back)
        WriteReg(MABBIPG, 0x12);
        // Set the maximum frame size which the controller will accept
        // Do not send frames longer than MAX_FRAMELEN:
        WriteReg(MAMXFLL, MAX_FRAMELEN & 0xFF);
        WriteReg(MAMXFLH, MAX_FRAMELEN >> 8);
        // do bank 3 stuff
        // write MAC address
        // NOTE: MAC address in ENC28J60 is byte-backward
        WriteReg(MAADR5, macaddr[0]);
        WriteReg(MAADR4, macaddr[1]);
        WriteReg(MAADR3, macaddr[2]);
        WriteReg(MAADR2, macaddr[3]);
        WriteReg(MAADR1, macaddr[4]);
        WriteReg(MAADR0, macaddr[5]);
        // no loopback of transmitted frames
        WritePhyReg(PHCON2, PHCON2_HDLDIS);

        // enabling link interrupts could be interesting, down
        // seem not to be firing though
        WritePhyReg(PHIE, (ushort)(ReadPhyReg(PHIE) | 18));

        // switch to bank 0
        CurrentBank = ECON1;
        // enable interrutps
        WriteOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE | EIE_PKTIE);
        // enable frame reception
        WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
    }

    /// <summary>
    /// Send an ehternet frame to the network
    /// </summary>
    /// <param name="len">Size of the frame</param>
    /// <param name="frame">Byte array containing frame</param>
    public void SendFrame(byte[] frame)
    {
        // only write and send one frame at a time
        lock (irq)
        {

            int len = frame.Length;

            // Set the write pointer to start of transmit buffer area
            WriteReg(EWRPTL, TXSTART_INIT & 0xFF);
            WriteReg(EWRPTH, TXSTART_INIT >> 8);

            //// TX start
            //WriteReg(ETXSTL, TXSTART_INIT & 0xFF);
            //WriteReg(ETXSTH, TXSTART_INIT >> 8);            

            // Set the TXND pointer to correspond to the frame size given
            WriteReg(ETXNDL, (byte)((TXSTART_INIT + len) & 0xFF));
            WriteReg(ETXNDH, (byte)((TXSTART_INIT + len) >> 8));
            // write per-frame control byte (0x00 means use macon3 settings)
            WriteOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);
            // copy the frame into the transmit buffer
            WriteBuffer(frame);
            // send the contents of the transmit buffer onto the network
            WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
            // Reset the transmit logic problem. See Rev. B4 Silicon Errata point 12.
            if ((ReadReg(EIR) & EIR_TXERIF) != 0)
            {
                WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS);
            }
        }
    }

    /// <summary>
    /// Fetches parts of an ethernet frame from the network receive buffer, if one is available.
    /// </summary>
    /// <returns>Frame data in a new byte array.  Returns null if there are no frames in the buffer</returns>
    private byte[] ReceiveFrame()
    {
        byte[] frame = null;

        ushort rxstat;
        ushort len;
        // check if a frame has been received and buffered
        //if( !(ReadReg(EIR) & EIR_PKTIF) ){
        // The above does not work. See Rev. B4 Silicon Errata point 6.
        if (ReadReg(EPKTCNT) == 0) return frame;  // Packet count in the buffer is 0, so there is nothing to do.  

        // Set the read pointer to the start of the received frame
        WriteReg(ERDPTL, (byte)NextframePtr);
        WriteReg(ERDPTH, (byte)(NextframePtr >> 8));
        
        // read the next frame pointer
        NextframePtr = ReadOp(ENC28J60_READ_BUF_MEM, 0);
        NextframePtr |= (ushort)(ReadOp(ENC28J60_READ_BUF_MEM, 0) << 8);
        
        // read the frame length (see datasheet page 43)
        len = ReadOp(ENC28J60_READ_BUF_MEM, 0);
        len |= (ushort)(ReadOp(ENC28J60_READ_BUF_MEM, 0) << 8);
        len -= 4; //remove the CRC count
        
        // read the receive status (see datasheet page 43)
        rxstat = ReadOp(ENC28J60_READ_BUF_MEM, 0);
        rxstat |= (ushort)(ReadOp(ENC28J60_READ_BUF_MEM, 0) << 8);
        
        // check CRC and symbol errors (see datasheet page 44, table 7-3):
        // The ERXFCON.CRCEN is set by default. Normally we should not
        // need to check this.
        if ((rxstat & 0x80) != 0)
        {
            frame = new byte[len];
            ReadBuffer(len, frame);   // copy the frame from the receive buffer
        }

        // Move the RX read pointer to the start of the next received frame
        // This frees the memory we just read out
        WriteReg(ERXRDPTL, (byte)NextframePtr);
        WriteReg(ERXRDPTH, (byte)(NextframePtr >> 8));
        // decrement the frame counter indicate we are done with this frame
        WriteOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);

        return frame;
    }


    // ENC28J60 Control Registers
    // Control register definitions are a combination of address,
    // bank number, and Ethernet/MAC/PHY indicator bits.
    // - Register address        (bits 0-4)
    // - Bank number        (bits 5-6)
    // - MAC/PHY indicator        (bit 7)
    const byte ADDR_MASK = 0x1F;
    const byte BANK_MASK = 0x60;
    const byte SPRD_MASK = 0x80;
    // All-bank registers
    const byte EIE = 0x1B;
    const byte EIR = 0x1C;
    const byte ESTAT = 0x1D;
    const byte ECON2 = 0x1E;
    const byte ECON1 = 0x1F;
    // Bank 0 registers
    const byte ERDPTL = (0x00 | 0x00);
    const byte ERDPTH = (0x01 | 0x00);
    const byte EWRPTL = (0x02 | 0x00);
    const byte EWRPTH = (0x03 | 0x00);
    const byte ETXSTL = (0x04 | 0x00);
    const byte ETXSTH = (0x05 | 0x00);
    const byte ETXNDL = (0x06 | 0x00);
    const byte ETXNDH = (0x07 | 0x00);
    const byte ERXSTL = (0x08 | 0x00);
    const byte ERXSTH = (0x09 | 0x00);
    const byte ERXNDL = (0x0A | 0x00);
    const byte ERXNDH = (0x0B | 0x00);
    const byte ERXRDPTL = (0x0C | 0x00);
    const byte ERXRDPTH = (0x0D | 0x00);
    const byte ERXWRPTL = (0x0E | 0x00);
    const byte ERXWRPTH = (0x0F | 0x00);
    const byte EDMASTL = (0x10 | 0x00);
    const byte EDMASTH = (0x11 | 0x00);
    const byte EDMANDL = (0x12 | 0x00);
    const byte EDMANDH = (0x13 | 0x00);
    const byte EDMADSTL = (0x14 | 0x00);
    const byte EDMADSTH = (0x15 | 0x00);
    const byte EDMACSL = (0x16 | 0x00);
    const byte EDMACSH = (0x17 | 0x00);
    // Bank 1 registers
    const byte EHT0 = (0x00 | 0x20);
    const byte EHT1 = (0x01 | 0x20);
    const byte EHT2 = (0x02 | 0x20);
    const byte EHT3 = (0x03 | 0x20);
    const byte EHT4 = (0x04 | 0x20);
    const byte EHT5 = (0x05 | 0x20);
    const byte EHT6 = (0x06 | 0x20);
    const byte EHT7 = (0x07 | 0x20);
    const byte EPMM0 = (0x08 | 0x20);
    const byte EPMM1 = (0x09 | 0x20);
    const byte EPMM2 = (0x0A | 0x20);
    const byte EPMM3 = (0x0B | 0x20);
    const byte EPMM4 = (0x0C | 0x20);
    const byte EPMM5 = (0x0D | 0x20);
    const byte EPMM6 = (0x0E | 0x20);
    const byte EPMM7 = (0x0F | 0x20);
    const byte EPMCSL = (0x10 | 0x20);
    const byte EPMCSH = (0x11 | 0x20);
    const byte EPMOL = (0x14 | 0x20);
    const byte EPMOH = (0x15 | 0x20);
    const byte EWOLIE = (0x16 | 0x20);
    const byte EWOLIR = (0x17 | 0x20);
    const byte ERXFCON = (0x18 | 0x20);
    const byte EPKTCNT = (0x19 | 0x20);
    // Bank 2 registers
    const byte MACON1 = (0x00 | 0x40 | 0x80);
    const byte MACON2 = (0x01 | 0x40 | 0x80);
    const byte MACON3 = (0x02 | 0x40 | 0x80);
    const byte MACON4 = (0x03 | 0x40 | 0x80);
    const byte MABBIPG = (0x04 | 0x40 | 0x80);
    const byte MAIPGL = (0x06 | 0x40 | 0x80);
    const byte MAIPGH = (0x07 | 0x40 | 0x80);
    const byte MACLCON1 = (0x08 | 0x40 | 0x80);
    const byte MACLCON2 = (0x09 | 0x40 | 0x80);
    const byte MAMXFLL = (0x0A | 0x40 | 0x80);
    const byte MAMXFLH = (0x0B | 0x40 | 0x80);
    const byte MAPHSUP = (0x0D | 0x40 | 0x80);
    const byte MICON = (0x11 | 0x40 | 0x80);
    const byte MICMD = (0x12 | 0x40 | 0x80);
    const byte MIREGADR = (0x14 | 0x40 | 0x80);
    const byte MIWRL = (0x16 | 0x40 | 0x80);
    const byte MIWRH = (0x17 | 0x40 | 0x80);
    const byte MIRDL = (0x18 | 0x40 | 0x80);
    const byte MIRDH = (0x19 | 0x40 | 0x80);
    // Bank 3 registers
    const byte MAADR1 = (0x00 | 0x60 | 0x80);
    const byte MAADR0 = (0x01 | 0x60 | 0x80);
    const byte MAADR3 = (0x02 | 0x60 | 0x80);
    const byte MAADR2 = (0x03 | 0x60 | 0x80);
    const byte MAADR5 = (0x04 | 0x60 | 0x80);
    const byte MAADR4 = (0x05 | 0x60 | 0x80);
    const byte EBSTSD = (0x06 | 0x60);
    const byte EBSTCON = (0x07 | 0x60);
    const byte EBSTCSL = (0x08 | 0x60);
    const byte EBSTCSH = (0x09 | 0x60);
    const byte MISTAT = (0x0A | 0x60 | 0x80);
    const byte EREVID = (0x12 | 0x60);
    const byte ECOCON = (0x15 | 0x60);
    const byte EFLOCON = (0x17 | 0x60);
    const byte EPAUSL = (0x18 | 0x60);
    const byte EPAUSH = (0x19 | 0x60);
    // PHY registers
    const byte PHCON1 = 0x00;
    const byte PHSTAT1 = 0x01;
    const byte PHHID1 = 0x02;
    const byte PHHID2 = 0x03;
    const byte PHCON2 = 0x10;
    const byte PHSTAT2 = 0x11;
    const byte PHIE = 0x12;
    const byte PHIR = 0x13;
    const byte PHLCON = 0x14;

    // ENC28J60 ERXFCON Register Bit Definitions
    const byte ERXFCON_UCEN = 0x80;
    const byte ERXFCON_ANDOR = 0x40;
    const byte ERXFCON_CRCEN = 0x20;
    const byte ERXFCON_PMEN = 0x10;
    const byte ERXFCON_MPEN = 0x08;
    const byte ERXFCON_HTEN = 0x04;
    const byte ERXFCON_MCEN = 0x02;
    const byte ERXFCON_BCEN = 0x01;
    // ENC28J60 EIE Register Bit Definitions
    const byte EIE_INTIE = 0x80;
    const byte EIE_PKTIE = 0x40;
    const byte EIE_DMAIE = 0x20;
    const byte EIE_LINKIE = 0x10;
    const byte EIE_TXIE = 0x08;
    const byte EIE_WOLIE = 0x04;
    const byte EIE_TXERIE = 0x02;
    const byte EIE_RXERIE = 0x01;
    // ENC28J60 EIR Register Bit Definitions
    const byte EIR_PKTIF = 0x40;
    const byte EIR_DMAIF = 0x20;
    const byte EIR_LINKIF = 0x10;
    const byte EIR_TXIF = 0x08;
    const byte EIR_WOLIF = 0x04;
    const byte EIR_TXERIF = 0x02;
    const byte EIR_RXERIF = 0x01;
    // ENC28J60 ESTAT Register Bit Definitions
    const byte ESTAT_INT = 0x80;
    const byte ESTAT_LATECOL = 0x10;
    const byte ESTAT_RXBUSY = 0x04;
    const byte ESTAT_TXABRT = 0x02;
    const byte ESTAT_CLKRDY = 0x01;
    // ENC28J60 ECON2 Register Bit Definitions
    const byte ECON2_AUTOINC = 0x80;
    const byte ECON2_PKTDEC = 0x40;
    const byte ECON2_PWRSV = 0x20;
    const byte ECON2_VRPS = 0x08;
    // ENC28J60 ECON1 Register Bit Definitions
    const byte ECON1_TXRST = 0x80;
    const byte ECON1_RXRST = 0x40;
    const byte ECON1_DMAST = 0x20;
    const byte ECON1_CSUMEN = 0x10;
    const byte ECON1_TXRTS = 0x08;
    const byte ECON1_RXEN = 0x04;
    const byte ECON1_BSEL1 = 0x02;
    const byte ECON1_BSEL0 = 0x01;
    // ENC28J60 MACON1 Register Bit Definitions
    const byte MACON1_LOOPBK = 0x10;
    const byte MACON1_TXPAUS = 0x08;
    const byte MACON1_RXPAUS = 0x04;
    const byte MACON1_PASSALL = 0x02;
    const byte MACON1_MARXEN = 0x01;
    // ENC28J60 MACON2 Register Bit Definitions
    const byte MACON2_MARST = 0x80;
    const byte MACON2_RNDRST = 0x40;
    const byte MACON2_MARXRST = 0x08;
    const byte MACON2_RFUNRST = 0x04;
    const byte MACON2_MATXRST = 0x02;
    const byte MACON2_TFUNRST = 0x01;
    // ENC28J60 MACON3 Register Bit Definitions
    const byte MACON3_PADCFG2 = 0x80;
    const byte MACON3_PADCFG1 = 0x40;
    const byte MACON3_PADCFG0 = 0x20;
    const byte MACON3_TXCRCEN = 0x10;
    const byte MACON3_PHDRLEN = 0x08;
    const byte MACON3_HFRMLEN = 0x04;
    const byte MACON3_FRMLNEN = 0x02;
    const byte MACON3_FULDPX = 0x01;
    // ENC28J60 MICMD Register Bit Definitions
    const byte MICMD_MIISCAN = 0x02;
    const byte MICMD_MIIRD = 0x01;
    // ENC28J60 MISTAT Register Bit Definitions
    const byte MISTAT_NVALID = 0x04;
    const byte MISTAT_SCAN = 0x02;
    const byte MISTAT_BUSY = 0x01;
    // ENC28J60 PHY PHCON1 Register Bit Definitions
    const ushort PHCON1_PRST = 0x8000;
    const ushort PHCON1_PLOOPBK = 0x4000;
    const ushort PHCON1_PPWRSV = 0x0800;
    const ushort PHCON1_PDPXMD = 0x0100;
    // ENC28J60 PHY PHSTAT1 Register Bit Definitions
    const ushort PHSTAT1_PFDPX = 0x1000;
    const ushort PHSTAT1_PHDPX = 0x0800;
    const ushort PHSTAT1_LLSTAT = 0x0004;
    const ushort PHSTAT1_JBSTAT = 0x0002;
    // ENC28J60 PHY PHSTAT2 Register Bit Definitions
    const ushort PHSTAT2_PFDPX = 0x4000;
    const ushort PHSTAT2_TXSTAT = 0x2000;
    const ushort PHSTAT2_RXSTAT = 0x1000;
    const ushort PHSTAT2_COLSTAT = 0x0800;
    const ushort PHSTAT2_LSTAT = 0x0400;
    const ushort PHSTAT2_DPXSTAT = 0x0200;
    const ushort PHSTAT2_PLRITY = 0x0020;

    // ENC28J60 PHY PHCON2 Register Bit Definitions
    const ushort PHCON2_FRCLINK = 0x4000;
    const ushort PHCON2_TXDIS = 0x2000;
    const ushort PHCON2_JABBER = 0x0400;
    const ushort PHCON2_HDLDIS = 0x0100;

    // ENC28J60 Packet Control Byte Bit Definitions
    const byte PKTCTRL_PHUGEEN = 0x08;
    const byte PKTCTRL_PPADEN = 0x04;
    const byte PKTCTRL_PCRCEN = 0x02;
    const byte PKTCTRL_POVERRIDE = 0x01;

    // SPI operation codes
    const byte ENC28J60_READ_CTRL_REG = 0x00;
    const byte ENC28J60_READ_BUF_MEM = 0x3A;
    const byte ENC28J60_WRITE_CTRL_REG = 0x40;
    const byte ENC28J60_WRITE_BUF_MEM = 0x7A;
    const byte ENC28J60_BIT_FIELD_SET = 0x80;
    const byte ENC28J60_BIT_FIELD_CLR = 0xA0;
    const byte ENC28J60_SOFT_RESET = 0xFF;


    // The RXSTART_INIT should be zero. See Rev. B4 Silicon Errata
    // buffer boundaries applied to internal 8K ram
    // the entire available packet buffer space is allocated
    //
    // start with recbuf at 0/
    const ushort RXSTART_INIT = 0x0000;
    // receive buffer end
    const ushort RXSTOP_INIT = 0x19F0;  //(0x1FFF - 0x0600 - 1);

    // ushort TX buffer at 0x1FFF-0x0600, pace for one full ethernet frame (~1500 bytes)
    const ushort TXSTART_INIT = 0x1AFF; //(0x1FFF - 0x0600);
    // stp TX buffer at end of mem
    const ushort TXSTOP_INIT = 0x1FFF;
    //
    // max frame length which the conroller will accept:
    const ushort MAX_FRAMELEN = 1500;        // (note: maximum ethernet frame length would be 1518)
    //const byte MAX_FRAMELEN     600

}

